/*
 * Copyright (c) 2009, Hamish Morgan. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the University of Sussex nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package locusts.server.admin;

import javax.naming.OperationNotSupportedException;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.table.AbstractTableModel;
import locusts.common.ObservableList;
import locusts.common.Player;
import locusts.common.entities.Entity;
import locusts.server.Game;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A Table model that wraps a list of players from a game object. It allows
 * the players properties to be displayed and edited in a JTable.
 * 
 * @author Hamish Morgan
 */
public class PlayersTableModel extends AbstractTableModel {

    private static final Logger LOG = LoggerFactory.getLogger(
            PlayersTableModel.class);
//    //
    private final Column idColumn = new IdColumn();
    private final Column nameColumn = new NameColumn();
    private final Column scoreColumn = new ScoreColumn();
    private final Column killsColumn = new KillsColumn();
    private final Column timeColumn = new TimeColumn();
    private final Column damageColumn = new DamageColumn();
//    private final Column cropsColumn = new CropsColumn();
    private final Column farmIdColumn = new FarmColumn();
    private final Column[] columnOrder = new Column[]{nameColumn, idColumn,
        killsColumn, damageColumn, timeColumn, scoreColumn, farmIdColumn};
    private final ObservableList<Player> players;
    private final Game game;

    public PlayersTableModel(Game game) {
        this.game = game;
        players = game.getPlayers();
        players.addListDataListener(new PlayerListDataHandler());
    }

    private class PlayerListDataHandler implements ListDataListener {

        public void intervalAdded(ListDataEvent e) {
            fireTableRowsInserted(e.getIndex0(), e.getIndex1());
        }

        public void intervalRemoved(ListDataEvent e) {
            fireTableRowsDeleted(e.getIndex0(), e.getIndex1());
        }

        public void contentsChanged(ListDataEvent e) {
            fireTableRowsDeleted(e.getIndex0(), e.getIndex1());
        }
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return columnOrder[columnIndex].isEditable();
    }

    public int getRowCount() {
        return players.size();
    }

    public int getColumnCount() {
        return columnOrder.length;
    }

    public Object getValueAt(int rowIndex, int columnIndex) {
        try {
            return columnOrder[columnIndex].getValue(players.get(rowIndex));
        } catch (java.lang.IndexOutOfBoundsException e) {
            // this is a concurrency issue but should self correct
            return null;
        }
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        boolean updated = columnOrder[columnIndex].setValue(players.get(
                rowIndex), aValue);
        if (updated) {
            fireTableCellUpdated(rowIndex, columnIndex);
        }
    }

    @Override
    public String getColumnName(int column) {
        return columnOrder[column].getName();
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return columnOrder[columnIndex].getValueClass();
    }

    protected abstract static class Column {

        private String name;
        protected Class<?> c;

        public Column(Class<?> c, String name) {
            this.name = name;
            this.c = c;
        }

        public String getName() {
            return name;
        }

        public boolean isEditable() {
            return true;
        }

        public Class<?> getValueClass() {
            return c;
        }

        public abstract Object getValue(Player p);

        public abstract boolean setValue(Player p, Object value);
    }

    protected class IdColumn extends Column {

        public IdColumn() {
            super(Integer.class, "Id");
        }

        @Override
        public Object getValue(Player p) {
            return p.getId();
        }

        public boolean setValue(Player p, Object value) {
            throw new RuntimeException(new OperationNotSupportedException());
        }

        @Override
        public boolean isEditable() {
            return false;
        }
    }

    protected class NameColumn extends Column {

        public NameColumn() {
            super(String.class, "Name");
        }

        @Override
        public Object getValue(Player p) {
            return p.getName();
        }

        public boolean setValue(Player p, Object value) {
            final String newValue = (String) value;
            if (!newValue.equals(p.getName())) {
                p.setName(newValue);
//                p.setModified(true);
                return true;
            } else return false;
        }
    }

//    protected class CropsColumn extends Column {
//
//        public CropsColumn() {
//            super(Double.class, "Crops");
//        }
//
//        @Override
//        public Object getValue(Player p) {
//            return new Double(p.getCrops());
//        }
//
//        public boolean setValue(Player p, Object value) {
//            final double newValue = (Double) value;
//            if (newValue != p.getCrops()) {
//                p.setCrops(newValue);
//                p.setModified(true);
//                return true;
//            } else {
//                return false;
//            }
//        }
//    }
    protected class KillsColumn extends Column {

        public KillsColumn() {
            super(Integer.class, "Kills");
        }

        @Override
        public Object getValue(Player e) {
            return new Integer(e.getKills());
        }

        public boolean setValue(Player e, Object value) {
            final int newValue = (Integer) value;
            if (newValue != e.getKills()) {
                e.setKills(newValue);
//                e.setModified(true);
                return true;
            } else {
                return false;
            }
        }
    }

    protected class DamageColumn extends Column {

        public DamageColumn() {
            super(Double.class, "Damage");
        }

        @Override
        public Object getValue(Player e) {
            return new Double(e.getDamage());
        }

        public boolean setValue(Player e, Object value) {
            final double newValue = (Double) value;
            if (newValue != e.getDamage()) {
                e.setDamage(newValue);
//                e.setModified(true);
                return true;
            } else {
                return false;
            }
        }
    }

    protected class TimeColumn extends Column {

        public TimeColumn() {
            super(Double.class, "Time");
        }

        @Override
        public Object getValue(Player e) {
            return new Double(e.getTime());
        }

        public boolean setValue(Player e, Object value) {
            final double newValue = (Double) value;
            if (newValue != e.getTime()) {
                e.setTime(newValue);
//                e.setModified(true);
                return true;
            } else {
                return false;
            }
        }
    }

    protected class ScoreColumn extends Column {

        public ScoreColumn() {
            super(Double.class, "Score");
        }

        @Override
        public Object getValue(Player e) {
            return new Double(e.getScore());
        }

        public boolean setValue(Player p, Object value) {
            throw new RuntimeException(new OperationNotSupportedException());
        }

        @Override
        public boolean isEditable() {
            return false;
        }
    }

    protected class FarmColumn extends Column {

        public FarmColumn() {
            super(Entity.class, "Farm");
        }

        @Override
        public Object getValue(Player e) {
            final int id = e.getFarmId();
            if (id == -1)
                return null;
            else
                return game.getEntities().getById(id);
        }

        public boolean setValue(Player player, Object entity) {
            final int newValue;
            if (entity == null)
                newValue = -1;
            else newValue = ((Entity) entity).getId();

            if (newValue != player.getFarmId()) {
                player.setFarmId(newValue);
//                player.setModified(true);
                return true;
            } else {
                return false;
            }
        }
    }
}
